package com.example.networktest;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.content.Context;
import java.util.Enumeration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiConfiguration;
import android.net.DhcpInfo;
import android.text.format.Formatter;
import java.net.NetworkInterface;
import java.net.InetAddress; import java.net.SocketAddress; import java.net.SocketException;
import java.net.Socket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.File;
import java.util.*;


      //String[] files = workingDir.list();
      //Log.d(LOG_DEBUG_MSG, "Number of things in Working Directory: "+Integer.toString(files.length));
      //for (int i = 0; i < files.length; i++) 
      //{
      //  Log.d(LOG_DEBUG_MSG, "Contents of Working Directory: "+files[i]);
      //}


/*
 * This class will handle all network communications.  Basically this class 
 * will keep files synced between phones, and will be controlled by android
 * apps that use it to communicate.  This class will rely on another library
 * for authentication.
 */
public class networkingTestService
{
  // Debugging
  private static final String TAG = "networkingTestService";
  private static final boolean D = true;

  // Name for the SDP record when creating server socket
  private static final String NAME = "networkingTestService";

  // Member fields
  private final BluetoothAdapter mAdapter;
  private final Handler mHandler;
  public int mBTState;
  private int mState;
  private String mID;
  private String localIPAddress;
  private WifiManager wifiManager;
  private File workingDir;
  private Map<String, String> peerIPs = new HashMap<String, String>();
  private ArrayList peerRefresh = new ArrayList(); 


  // Constants that indicate the current connection state
  public static final int STATE_NONE = 0;       // we're doing nothing
  public static final int STATE_BTLISTEN = 1;     // now listening for incoming connections on bluetooth
  public static final int STATE_BTCONNECTING = 2; // now initiating an outgoing connection on bluetooth
  public static final int STATE_BTCONNECTED = 3;  // now connected to a remote device on bluetooth

  // Server Communication Thread
  private serverComThread servThread;
  private peerComThread peerThread;
  private peerSyncThread peerSThread;
  
  public static final String LOG_LOCALIP = "Exception Occured While trying to determine local ip.";
  public static final String LOG_DEBUG_MSG = "THIS IS A DEBUGGING MESSAGE --------------->";

  /**
   * Constructor. Creates a new networkTestService session.
   * @param context  The UI Activity Context
   * @param handler  A Handler to send messages back to the UI Activity
   */
  public networkingTestService(Context context, Handler handler) 
  {
    mID = "12";
    peerRefresh.add("12");
    peerRefresh.add("13");

    Log.d(LOG_DEBUG_MSG, "Starting up Network Service");
    wifiManager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
    mAdapter = BluetoothAdapter.getDefaultAdapter();
    mState = STATE_NONE;
    mBTState = STATE_NONE;
    mHandler = handler;

    Log.d(LOG_DEBUG_MSG, "Starting up Server Thread");
    servThread = new serverComThread();
    servThread.start();

    Log.d(LOG_DEBUG_MSG, "Starting up Peer Server Thread");
    peerThread = new peerComThread();
    peerThread.start();

    Log.d(LOG_DEBUG_MSG, "Starting up Peer Syncing Thread");
    peerSThread = new peerSyncThread(12);
    peerSThread.start();

    workingDir = new File("./sdcard");
  }

  public String getLocalIPAddress() 
  {
    return localIPAddress;
  } 
  /**
   * Determine IP address of the local phone, if it returns NULL, then there is no connection available.
   * This method returns 0 if there is an available connection and 1 otherwise.
   */
  public synchronized int refreshLocalIPAddress() 
  {
    WifiInfo wifiInfo = wifiManager.getConnectionInfo();
    DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
    int ipAddress = wifiInfo.getIpAddress();
    localIPAddress = Formatter.formatIpAddress(dhcpInfo.ipAddress)+"and"+Formatter.formatIpAddress(ipAddress);

    return 0;

  }

  // This thread connects to the server, and recieves information about 
  // all the IDs passed to it
  private class serverComThread extends Thread 
  {
    // Socket used to communicate with the server

    private String address;
    private Socket mainSocket;
    private int port;
    private int connected;
    private InputStream servIn;
    private OutputStream servOut;

    public serverComThread()
    {
      mainSocket=null;
      address = "192.168.1.106";
      port = 7778;
      servIn=null;
      servOut=null;
      connected=0;
    }

    public void run()
    {
      // See if there are any peers whose IPs need to refreshed
      if(peerRefresh.size()==0)
        return ;
      // Connect to the server
      int output = connectToServer();
      Log.d(LOG_DEBUG_MSG, "Connection status is "+Integer.toString(output));
      if(output !=0)
      {
        Log.d(LOG_DEBUG_MSG, "Canceling");
        return ;
      }
      
      Object ia[] = peerRefresh.toArray();
      for(int i=0; i<ia.length; i++)
      {
        // Fetch ip addresses
        int status = getPeerIP((String)ia[i]);
        if(status == 0)
          peerRefresh.remove((String)ia[i]);
        else if(status != 2)
          return ;
      }

      Log.d(LOG_DEBUG_MSG, "Closing connection to server"+Integer.toString(output));
      // Close Connection to server
      try
      {
        servOut.write("-1".getBytes());
      }
      catch (IOException e) 
      {
        Log.e(TAG, "close() of socket failed", e);
      }

      return ;
    }

    public void cancel()
    {
      Log.d(LOG_DEBUG_MSG, "Server Thread Shutting Down");
      Log.d(LOG_DEBUG_MSG, "Socket status is "+Boolean.toString(mainSocket!=null));
      Log.d(LOG_DEBUG_MSG, "Connection status is "+Integer.toString(connected));

      if(mainSocket!=null && connected==1)
      {
        if(!mainSocket.isConnected())
        {
          try
          {
            mainSocket.close();
          }
          catch (IOException e) 
          {
            Log.e(TAG, "close() of socket failed", e);
          }
        }
      }
      mainSocket=null;
      Log.d(LOG_DEBUG_MSG, "Exiting");
    }

    // Recieves a message from the server that is no longer than
    // maxLength bytes
    public String recvMessageFromServer(int maxLength)
    {
      try
      {
        // If we are not connected to the server, then return an empty string
        if(mainSocket==null || !mainSocket.isConnected() || servIn==null)
          return "";

        // Read the message, and record its length
        int byteNum=0;
        byte[] msg = new byte[maxLength];
        byteNum = servIn.read(msg,0,maxLength);
        if(byteNum==maxLength)
        {
          Log.d(LOG_DEBUG_MSG, "the message received from the server was truncated.");
          return new String(msg);
        }
        Log.d(LOG_DEBUG_MSG, "Recieved message length is "+Integer.toString(byteNum));
        // Then put the message in a smaller array and convert that array to 
        // a string
        if(byteNum==0)
          return "";
        byte[] trimmedMsg = new byte[byteNum];
        System.arraycopy(msg, 0, trimmedMsg, 0, byteNum);
        return new String(trimmedMsg);
      }
      catch(IOException ex)
      {
        Log.d(LOG_DEBUG_MSG, "Unable to recieve message from server");
        Log.e(LOG_LOCALIP, ex.toString());
        return "";
      }
    }

    // Get the IP address of the given peer from the server
    public int getPeerIP(String peerID)
    {
      try
      {
        // Send the server the ID we want info about
        servOut.write(peerID.getBytes());
        // Recieve the server's response
        String Message = recvMessageFromServer(256);
        Log.d(LOG_DEBUG_MSG, "Recieved message "+Message);
        // If this peer is online
        if(Message.compareTo("i")==0)
        {
          // Recieve the IP and Port
          String IP = recvMessageFromServer(256);
          Log.d(LOG_DEBUG_MSG, "The IP is "+IP);
          peerIPs.put(peerID,IP);
          return 0;
        }
        else
        {
          peerIPs.put(peerID,"OFFLINE");
          return 2;
        }
      }
      catch(IOException ex)
      {
        Log.d(LOG_DEBUG_MSG, "Unable to get Peer IP");
        Log.e(LOG_LOCALIP, ex.toString());
        return 1;
      }
    }

    public int connectToServer()
    {
      try
      {
        Log.d(LOG_DEBUG_MSG, "Trying to connect to "+address);
        // Open the socket if needed
        if(mainSocket==null) mainSocket = new Socket(address,port);

        servIn = mainSocket.getInputStream();
        servOut = mainSocket.getOutputStream();

        // Send the server our id
        servOut.write(mID.getBytes());
        // Recieve an acknowledgement from the server
        String Message = recvMessageFromServer(10);
        Log.d(LOG_DEBUG_MSG, "Recieved message "+Message);
        if(Message.compareTo("r")==0)
        {
          connected=1;
          return 0;
        }
        else
          return 1;
      }
      catch(IOException ex)
      {
        Log.d(LOG_DEBUG_MSG, "Unable to connect to server");
        Log.e(LOG_LOCALIP, ex.toString());
        return 1;
      }
    }
  }

  // This thread waits for a peer to connect to it
  private class peerComThread extends Thread 
  {
    private SocketAddress inet;
    private int done;
    private ServerSocket peerSocket;
    // Socket used to open communication with other peers
    public peerComThread()
    {
      //try
      //{
      //  peerSocket = new ServerSocket(7779);
      //}
      //catch (IOException e) 
      //{
      //  Log.e(TAG, "Failed to open peer request server socket", e);
      //}
      //done=0;
    }

    public void run()
    {
      try
      {
        Log.d(LOG_DEBUG_MSG, "Creating Server Socket");
        peerSocket = new ServerSocket(7779);
        Log.d(LOG_DEBUG_MSG, "waiting for peer connection");
        while(done==0)
        {
          Socket peerCom = peerSocket.accept();
          Log.d(LOG_DEBUG_MSG, "peer connection recieved");
          Log.d(LOG_DEBUG_MSG, "Recieved message is "+recvMessageFromPeer(peerCom,10));
          peerSocket.close();
        }
      }
      catch (IOException e)
      {
        Log.e(TAG, "failed to connect to peer", e);
      }
      return ;
    }

    // Recieves a message from the server that is no longer than
    // maxLength bytes
    public String recvMessageFromPeer(Socket SocketIn, int maxLength)
    {
      try
      {
        InputStream servIn = SocketIn.getInputStream();
        OutputStream servOut = SocketIn.getOutputStream();
        // If we are not connected to the server, then return an empty string
        if(servIn==null)
          return "";

        // Read the message, and record its length
        int byteNum=0;
        byte[] msg = new byte[maxLength];
        byteNum = servIn.read(msg,0,maxLength);
        if(byteNum==maxLength)
        {
          Log.d(LOG_DEBUG_MSG, "the message received from the server was truncated.");
          return new String(msg);
        }
        Log.d(LOG_DEBUG_MSG, "Recieved message length is "+Integer.toString(byteNum));
        // Then put the message in a smaller array and convert that array to 
        // a string
        if(byteNum==0)
          return "";
        byte[] trimmedMsg = new byte[byteNum];
        System.arraycopy(msg, 0, trimmedMsg, 0, byteNum);
        return new String(trimmedMsg);
      }
      catch(IOException ex)
      {
        Log.d(LOG_DEBUG_MSG, "Unable to recieve message from server");
        Log.e(LOG_LOCALIP, ex.toString());
        return "";
      }
    }

    public void cancel()
    {
      Log.d(LOG_DEBUG_MSG, "Peer Server Thread Shutting Down");
      Log.d(LOG_DEBUG_MSG, "Socket status is "+Boolean.toString(peerSocket!=null));

      try
      {
        peerSocket.close();
      }
      catch (IOException e) 
      {
        Log.e(TAG, "close() of socket failed", e);
      }
      peerSocket=null;
      Log.d(LOG_DEBUG_MSG, "Exiting");
    }
  }


  // This syncs with the given peer
  private class peerSyncThread extends Thread 
  {
    private Socket peerSocket;
    private InputStream servIn;
    private OutputStream servOut;
    private Map<String, String> FileTimeStamps = new HashMap<String, String>();
    private int peerID;
    private InputStream peerIn;
    private OutputStream peerOut;
    // Socket used to open communication with other peers
    public peerSyncThread(int id)
    {
      peerID = id;
    }

    public void run()
    {
      try
      {
        if(peerIPs.containsKey(peerID))
          return ;
        String IP = peerIPs.get(peerID);
        peerSocket = new Socket(IP,7779);

        peerIn = peerSocket.getInputStream();
        peerOut = peerSocket.getOutputStream();

        // Get the timestamps on the local machine
        //FileTimeStampsLocal = getModificationTimes();

        // Send the peer our ID
        peerOut.write(mID.getBytes());

        peerSocket.close();
        /*
        // Get number of files the peer knows about
        String msg = recvMessageFromPeer(10);
        int numFiles = Integer.parseInt(msg.trim());
        // if the peer sent us a -1, then we terminate communication
        if(numFiles==-1)
        {
          Socket.close();
          return ;
        }
        // Otherwise, we prepare to recv the filename timestamp strings
        for(int i=0; i<numFiles; i++)
        {
          String  filename = recvMessageFromPeer(10);
          String timestamp = recvMessageFromPeer(10);

          Log.d(LOG_DEBUG_MSG, "Filename: "+filename);
          Log.d(LOG_DEBUG_MSG, "Timestamp: "+timestamp);

          // If the filename does not exist on the local phone, then create it
          //if(FileTimeStampsPeer[filename] == null)

          // If the filename does exist, but the one on the phone is more recent
          // then update it
          //else if(FileTimeStampsPeer[filename] >= timestamp)
        }

        peerSocket.close();
        */
      }
      catch (IOException e) 
      {
        Log.e(TAG, "failed to connect to peer", e);
      }
      return ;
      /*
      // See if there are any peers whose IPs need to refreshed
      if(peerRefresh.size()==0)
        return ;
      // Connect to the server
      int output = connectToServer();
      Log.d(LOG_DEBUG_MSG, "Connection status is "+Integer.toString(output));
      if(output !=0)
      {
        Log.d(LOG_DEBUG_MSG, "Canceling");
        return ;
      }
      
      Object ia[] = peerRefresh.toArray();
      for(int i=0; i<ia.length; i++)
      {
        // Fetch ip addresses
        int status = getPeerIP((String)ia[i]);
        if(status == 0)
          peerRefresh.remove((String)ia[i]);
        else if(status != 2)
          return ;
      }

      Log.d(LOG_DEBUG_MSG, "Closing connection to server"+Integer.toString(output));
      // Close Connection to server
      try
      {
        servOut.write("-1".getBytes());
      }
      catch (IOException e) 
      {
        Log.e(TAG, "close() of socket failed", e);
      }

      return ;
      */
    }

    // Recieves a message from the server that is no longer than
    // maxLength bytes
    public String recvMessageFromPeer(int maxLength)
    {
      try
      {
        // If we are not connected to the server, then return an empty string
        if(servIn==null)
          return "";

        // Read the message, and record its length
        int byteNum=0;
        byte[] msg = new byte[maxLength];
        byteNum = servIn.read(msg,0,maxLength);
        if(byteNum==maxLength)
        {
          Log.d(LOG_DEBUG_MSG, "the message received from the server was truncated.");
          return new String(msg);
        }
        Log.d(LOG_DEBUG_MSG, "Recieved message length is "+Integer.toString(byteNum));
        // Then put the message in a smaller array and convert that array to 
        // a string
        if(byteNum==0)
          return "";
        byte[] trimmedMsg = new byte[byteNum];
        System.arraycopy(msg, 0, trimmedMsg, 0, byteNum);
        return new String(trimmedMsg);
      }
      catch(IOException ex)
      {
        Log.d(LOG_DEBUG_MSG, "Unable to recieve message from server");
        Log.e(LOG_LOCALIP, ex.toString());
        return "";
      }
    }

    public void cancel()
    {
      Log.d(LOG_DEBUG_MSG, "Peer Server Thread Shutting Down");
      Log.d(LOG_DEBUG_MSG, "Socket status is "+Boolean.toString(peerSocket!=null));

      try
      {
        peerSocket.close();
      }
      catch (IOException e) 
      {
        Log.e(TAG, "close() of socket failed", e);
      }
      peerSocket=null;
      Log.d(LOG_DEBUG_MSG, "Exiting");
    }
  }


}

